/**
 * 
 */
package prj3;

import java.util.GregorianCalendar;
import java.util.Iterator;

import prj3.exceptions.EmptyListException;
import prj3.exceptions.WrongQueryModeException;
import data.CellSample;
import data.Grid;
import data.GridSample;
import data.Serializer;

/**
 * @author romain
 *
 */
public class QueryEngine implements ISimulate, IQueryEngine {

	protected final double GEO_TOLERANCE = (float)Math.PI/15;
	protected final int MIN_TOLERANCE = 10;
	/* (non-Javadoc)
	 * @see prj3.ISimulate#iterator()
	 */
	public Iterator iterator() {
		return new SphericalGridIterator(currentData);
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#setSimulatedTimeStep(java.util.GregorianCalendar)
	 */
	public boolean setSimulatedTimeStep(GregorianCalendar timeStep) {
		temporalCount++;
		if(temporalCount < maxTemporal) {
			isStoringTemporal = false;
			return true;
		}
		isStoringTemporal = true;
		temporalCount = 0;
		
		currentSample = currentDatabase.getGridSampleFactory().create();
		currentSample.grid_id = currentGrid.getID();
		currentSample.time = new java.sql.Timestamp( timeStep.getTime().getTime() ); 
		currentTime = timeStep;
		
		currentSample.sun_theta = 0; // not really necessary for the sim to work
		currentSample.sun_phi = 0; // not really necessary for the sim to work
		currentSample.save();
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#setSunAngle(double)
	 */
	public boolean setSunAngle(double sunAngle) {
		// We don't care about this
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#setSunDistance(double)
	 */
	public boolean setSunDistance(double sunDistance) {
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#setSunLatitude(double)
	 */
	public boolean setSunLatitude(double sunAngle) {
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#setSunOrbitAngle(double)
	 */
	public boolean setSunOrbitAngle(double sunAngle) {
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#stepGrid()
	 */
	public void stepGrid() {
		// Nothing to do

	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#stepGrid(int)
	 */
	public void stepGrid(int chMinutes) {
		// Nothing to do
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#takeSimulatedTimeStep()
	 */
	public GregorianCalendar takeSimulatedTimeStep() throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.takeSimulatedTimeStep();
	}

	public GregorianCalendar getSimulatedTimeStep(int position) throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.getSimulatedTimeStep(position);
	}
	
	/* (non-Javadoc)
	 * @see prj3.ISimulate#takeSunAngle()
	 */
	public double takeSunAngle() throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.takeSunAngle();
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#takeSunDistance()
	 */
	public double takeSunDistance() throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.takeSunDistance();
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#takeSunLatitude()
	 */
	public double takeSunLatitude() throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.takeSunLatitude();
	}

	/* (non-Javadoc)
	 * @see prj3.ISimulate#takeSunOrbitAngle()
	 */
	public double takeSunOrbitAngle() throws EmptyListException {
		if(currentData == null)
			throw new EmptyListException();
		return currentData.takeSunOrbitAngle();
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMaxTemperature()
	 */
	public float getMaxTemperature() {
		Iterator it = currentData.iterator();
		
		if(curMaxCell != null)
			return curMaxValue;
		
		while(it.hasNext()) {
			BufferedEarthCell bCell = (BufferedEarthCell)it.next();
			if(bCell.getCoordinate().isInRange(startRange, latRange, longRange)) {
				// The cell is in range
				try {
					float maxValue = bCell.getMaximumValue(minTimePos, maxTimePos);
					if(maxValue > curMaxValue) {
						curMaxValue = maxValue;
						curMaxTime = bCell.getMaximumPosition(minTimePos, maxTimePos);
						curMaxCell = bCell;
					}
				} catch(EmptyListException ex) {
					// Go to the next cell but this is not good
				}
			}
		}
		return curMaxValue;
		
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMaxTemperatureLatitude()
	 */
	public float getMaxTemperatureLatitude() {
		if(curMaxCell == null)
			getMaxTemperature();
		
		return ((SphericalCoordinate)curMaxCell.getCoordinate()).getTheta();	
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMaxTemperatureLongitude()
	 */
	public float getMaxTemperatureLongitude() {
		if(curMaxCell == null)
			getMaxTemperature();
		
		return ((SphericalCoordinate)curMaxCell.getCoordinate()).getPhi();	
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMaxTemperatureTime()
	 */
	public GregorianCalendar getMaxTemperatureTime() {
		try {
			return currentData.getSimulatedTimeStep(curMaxTime);
		} catch(EmptyListException ex) {
			return null; // This is not good. Should never happen
		}
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMinTemperature()
	 */
	public float getMinTemperature() {
		Iterator it = currentData.iterator();
		
		if(curMinCell != null)
			return curMinValue;
		
		while(it.hasNext()) {
			BufferedEarthCell bCell = (BufferedEarthCell)it.next();
			if(bCell.getCoordinate().isInRange(startRange, latRange, longRange)) {
				// The cell is in range
				try {
					float minValue = bCell.getMinimumValue(minTimePos, maxTimePos);
					if(minValue < curMinValue) {
						curMinValue = minValue;
						curMinTime = bCell.getMinimumPosition(minTimePos, maxTimePos);
						curMinCell = bCell;
					}
				} catch(EmptyListException ex) {
					// Go to the next cell but this is not good
				}
			}
		}
		return curMinValue;
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMinTemperatureLatitude()
	 */
	public float getMinTemperatureLatitude() {
		if(curMinCell == null)
			getMinTemperature();
		
		return ((SphericalCoordinate)curMinCell.getCoordinate()).getTheta();
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMinTemperatureLongitude()
	 */
	public float getMinTemperatureLongitude() {
		if(curMinCell == null)
			getMinTemperature();
		
		return ((SphericalCoordinate)curMinCell.getCoordinate()).getPhi();	
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getMinTemperatureTime()
	 */
	public GregorianCalendar getMinTemperatureTime() {
		try {
			return currentData.getSimulatedTimeStep(curMinTime);
		} catch(EmptyListException ex) {
			return null; // This is not good. Should never happen
		}
	}

	
	
	public float getAvgTemperatureForCell(BufferedEarthCell cell) {
		try {
			return cell.getAverageValue(minTimePos, maxTimePos);
		} catch(EmptyListException ex) {
			return -1; // Not good
		}
			
	}

	public float getAvgTemperatureForTime(GregorianCalendar time) {
		Iterator it = currentData.iterator();
		
		long durationDiff = time.getTime().getTime() - realStartDate.getTime().getTime();
		durationDiff /= (1000*60);
		int pos = (int)durationDiff/currentTimeStep + minTimePos;
		
		int counter = 0;
		float value = 0;
		while(it.hasNext()) {
			BufferedEarthCell bCell = (BufferedEarthCell)it.next();
			if(bCell.getCoordinate().isInRange(startRange, latRange, longRange)) {
				counter++;
				try {
					value += bCell.getValueForPos(pos);
				} catch(EmptyListException ex) {
					return -1; // Not good
				}
			}
		}
		return value/(float)counter;
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#getSimulationData(float, float, java.util.GregorianCalendar, java.util.GregorianCalendar, float, float, float, float)
	 */
	public boolean getSimulationData(double tilt, double excentricity,
			double angularSeparation, int timeStep,
			GregorianCalendar startDate, GregorianCalendar endDate,
			double startLatitude, double endLatitude, double startLongitude,
			double endLongitude) {
		
		currentParameters = currentSerializer.new Parameters(angularSeparation,
				angularSeparation, timeStep, tilt, excentricity, startDate, endDate, 
				startLatitude, endLatitude, startLongitude, endLongitude,
				GEO_TOLERANCE, MIN_TOLERANCE, myPrecision);
		
		try {
			currentData = currentSerializer.load(currentParameters);
		} catch(data.InsufficientDataException ex) {
			currentData = null;
			return false;
		}
		
		// Calculate all the range and time parameters
		currentTimeStep = timeStep;
		realStartDate = startDate;
		long startDiff = startDate.getTime().getTime() - util.Constants.startDate.getTime().getTime();
		startDiff /= (1000*60); // Number of minutes of difference
		minTimePos = (int)startDiff/timeStep;
		
		long durationDiff = endDate.getTime().getTime() - startDate.getTime().getTime();
		durationDiff /= (1000*60);
		maxTimePos = (int)durationDiff/timeStep + minTimePos;
		startRange = new SphericalCoordinate(currentData, currentData.getRadius(), (float)startLongitude, (float)startLatitude);
		latRange = (float)(startLatitude - endLatitude);
		
		longRange = (float)(startLongitude - endLongitude);
		if(longRange < 0)
			longRange += Math.PI*2;
		
		curMaxValue = -9999;
		curMaxCell = null;
		curMaxTime = -1;
		curMinValue = 9999;
		curMinCell = null;
		curMinTime = -1;
		
		return true;
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#setInvocationParameters(float, float, int)
	 */
	public void setInvocationParameters(float percentTemporalStore,
			float percentGeoStore, int precision) {
		myPercentTemporalStore = percentTemporalStore;
		myPercentGeoStore = percentGeoStore;
		maxTemporal = (int)Math.round((100.0/(myPercentTemporalStore*100)));
		maxGeo = (int)Math.round((100.0/(myPercentGeoStore*100)));
		myPrecision = precision;
		
		currentDatabase = data.Database.GetInstance();
		currentSerializer = new Serializer(maxGeo, 
				maxTemporal, currentDatabase);
	}

	/* (non-Javadoc)
	 * @see prj3.IQueryEngine#startSimulationInput(float, float)
	 */
	public void startSimulationInput(float tilt, float excentricity, float gridStep, float timeStep) {
		currentGrid = currentDatabase.getGridFactory().create();
		currentGrid.param_precision_digits = myPrecision;
		currentGrid.param_geographic_accuracy = 0; // not really necessary for the sim to work
		currentGrid.param_temporal_accuracy = 0; // not really necessary for the sim to work
		currentGrid.phys_axial_tilt = tilt;
		currentGrid.phys_orbital_eccentricity = excentricity;
		currentGrid.sim_grid_spacing = gridStep; // not really necessary for the sim to work
		currentGrid.sim_time_step = (int)timeStep; // not really necessary for the sim to work
		currentGrid.save();
		
		isInputingData = true;
		temporalCount = 0; 
		geoCount = 0;
		currentData = null;

	}
	
	public void stopSimulationInput() {
		isInputingData = false;
		currentData = null;
	}
	
	public void inputCell(SphericalCell cell) throws WrongQueryModeException {
		if(!isInputingData)
			throw new WrongQueryModeException();
		
		if(!isStoringTemporal)
			return; // We don't store this TS
		
		// Take care of the geo stride
		geoCount++;
		if(geoCount < maxGeo)
			return; // We don't store this cell
		geoCount = 0; // Reset the count
		SphericalCoordinate coord = (SphericalCoordinate) cell.getCoordinate();

		CellSample cs = currentDatabase.getCellSampleFactory().create();
		cs.gridsample_id = currentSample.getID() + 1; // For some reason
		cs.grid_id = currentGrid.getID();
		cs.time = new java.sql.Timestamp( currentTime.getTime().getTime() );
		cs.coord_theta = coord.getTheta();
		cs.coord_phi = coord.getPhi();
		cs.coord_radius = coord.getRadius();
		cs.coord_latitude = coord.getLatitude();
		cs.coord_longitude = coord.getLongitude();
		cs.value = Math.floor((cell.getValue()*Math.pow(10, myPrecision)))/Math.pow(10, myPrecision);
		cs.save();
	}
	
	protected BufferedEarthGrid currentData;
	
	protected Serializer currentSerializer;
	protected Serializer.Parameters currentParameters;
	
	protected data.Database currentDatabase;
	protected Grid currentGrid;
	protected GridSample currentSample;
	
	protected boolean isInputingData;
	protected boolean isStoringTemporal;
	protected int temporalCount, geoCount, maxTemporal, maxGeo;
	
	protected GregorianCalendar currentTime;
	
	protected float myPercentTemporalStore, myPercentGeoStore;
	protected int myPrecision;
	
	protected GregorianCalendar realStartDate;
	protected int currentTimeStep;
	protected int minTimePos, maxTimePos;
	protected SphericalCoordinate startRange;
	protected float latRange, longRange;
	
	protected float curMaxValue;
	protected BufferedEarthCell curMaxCell;
	protected int curMaxTime;
	
	protected float curMinValue;
	protected BufferedEarthCell curMinCell;
	protected int curMinTime;
	

}
